package ru.massmo.trace;

import com.google.common.base.Joiner;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class TracingAspect {
    public static final Joiner ARGS_JOINER = Joiner.on(',').useForNull("null");

    @org.aspectj.lang.annotation.Before(
        value = "@annotation(trace)",
        argNames = "joinPoint, trace"
    )
	public void before(JoinPoint joinPoint, Trace trace) {
		Class<? extends Object> clazz = joinPoint.getTarget().getClass();
		String name = joinPoint.getSignature().getName();

        if (joinPoint.getArgs().length != 0 && trace.traceArgs()) {
            getLogger(clazz).trace("{}({})", name, ARGS_JOINER.join(joinPoint.getArgs()));
        } else {
            getLogger(clazz).trace("{}()", name);
        }
    }

	@org.aspectj.lang.annotation.AfterThrowing(
        value = "@annotation(trace)",
        throwing = "throwable",
        argNames = "joinPoint, throwable, trace"
    )
	public void afterThrowing(JoinPoint joinPoint, Throwable throwable, Trace trace) {
        if (!trace.traceExceptions()) {
            return;
        }

        Class<? extends Object> clazz = joinPoint.getTarget().getClass();
        String name = joinPoint.getSignature().getName();
        getLogger(clazz).trace(
            String.format("{%s}({%s}) thrown %s", name, ARGS_JOINER.join(joinPoint.getArgs()), throwable.getMessage()),
            throwable
        );
    }

	@org.aspectj.lang.annotation.AfterReturning(
        value = "@annotation(trace)",
        returning = "returnValue",
        argNames = "joinPoint, trace, returnValue"
    )
	public void afterReturning(JoinPoint joinPoint, Trace trace, Object returnValue) {
        if(!trace.traceReturning() && !trace.traceReturnValue()) {
            return;
        }

		Class<? extends Object> clazz = joinPoint.getTarget().getClass();
		String name = joinPoint.getSignature().getName();

		if (joinPoint.getSignature() instanceof MethodSignature) {
			MethodSignature signature = (MethodSignature) joinPoint.getSignature();
			Class<?> returnType = signature.getReturnType();
			if (returnType.getName().compareTo("void") == 0 || !trace.traceReturnValue()) {
				getLogger(clazz).trace("{}() =>", name);
				return;
			}
		}

        getLogger(clazz).trace("{}() => {}", name, returnValue);
	}

    private Logger getLogger(Class<? extends Object> clazz) {
        return LoggerFactory.getLogger(clazz);
    }
}
